using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using SharpChannel.Core.Packet;
using SharpChannel.Util;
using log4net;

namespace SharpChannel.Core
{
    public class TcpServer
    {
        private static ILog log = LoggerHelper.GetLogger();

        public delegate void SessionStateChanged(BaseSession session, Enums.SessionState state);

        public SessionStateChanged OnSessionStateChanged { get; set; }

        public TcpServer()
        {
            clients = new List<Socket>();
            client2Sessions = new ConcurrentDictionary<Socket, BaseSession>();
        }

        public void Start(int port = 0)
        {
            if (port <= 0) port = ConfigurationUtil.GetListenPort();
            log.Info("server is going to listen port:" + port);
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
            acceptThread = new Thread(RunServer);
            packetThread = new Thread(RunSelect);
            acceptThread.Start();
            log.Info("accept thread started success.");
            packetThread.Start();
            log.Info("packet thread started success.");
            log.Info("server started...");
        }

        private void RunServer()
        {
            serverSocket.Listen(10);
            status = ServerStatus.Running;
            while (status == ServerStatus.Running)
            {
                LoggerHelper.GetLogger().Info("server polling...");
                if (serverSocket.Poll(60000000, SelectMode.SelectRead))
                {
                    AcceptNewClient(serverSocket.Accept());
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
        }

        private void RunSelect()
        {
            status = ServerStatus.Running;
            List<Socket> readSockets = new List<Socket>();
            List<Socket> errorSockets = new List<Socket>();
            while (status == ServerStatus.Running)
            {
                if(clients == null || clients.Count <= 0)
                {
                    LoggerHelper.GetLogger().Debug("select ..." + clients.Count);
                    Thread.Sleep(10000);
                    continue;
                }
                LoggerHelper.GetLogger().Debug("select ..." + clients.Count);
                readSockets.Clear();
                errorSockets.Clear();
                readSockets.AddRange(clients);
                errorSockets.AddRange(clients);
                Socket.Select(readSockets, null, errorSockets, 10000000);
                foreach (Socket sock in readSockets)
                {
                    if (sock.Connected == false)
                    {
                        ShutDownClient(sock);
                        continue;
                    }
                    try
                    {
                        AcceptToThreadPool(sock);
                    }
                    catch (SocketException ex)
                    {
                        sock.Close();
                        errorSockets.Add(sock);
                        LoggerHelper.GetLogger().Error("run select error!", ex);
                        LoggerHelper.GetLogger().Error("a client socket is going to be removed..." + sock);
                    }
                }
                foreach (var sock in errorSockets)
                {
                    ShutDownClient(sock);
                }
            }
        }

        private void ShutDownClient(Socket sock)
        {
            clients.Remove(sock);
            while (client2Sessions.ContainsKey(sock))
            {
                BaseSession baseSession;
                if (!client2Sessions.TryRemove(sock, out baseSession)) continue;
                if (baseSession != null)
                {
                    baseSession.Deleted = 1;
                    OnSessionStateChanged?.Invoke(baseSession, Enums.SessionState.CLOSED);
                }
            }
            log.Info("shutting down client:" + sock);
        }

        private void AcceptNewClient(Socket client)
        {
            clients.Add(client);
            var session = new ServerSession(client);
            while (!client2Sessions.TryAdd(client, session)) { }
            OnSessionStateChanged?.Invoke(session, Enums.SessionState.CONNECTED);
            log.Info("new client connected..." + client);
        }

        private void AcceptToThreadPool(Socket socket)
        {
            if (!client2Sessions.ContainsKey(socket)) return;
            BaseSession session;
            client2Sessions.TryGetValue(socket, out session);
            if (session == null || session.Deleted != 0) return;
            var buffer = new byte[ServerRecieveBufferSize];
            var size = socket.Receive(buffer);
			if (size > 0) {
				ThreadPool.QueueUserWorkItem (AccpetMessage, new object[] {
					session,
					buffer,
					size
				});
			} else {
				ShutDownClient (socket);
			}
        }

        private void AccpetMessage(object args)
        {
            var argsArray = args as object[];
            if (argsArray == null) return;
            var session = argsArray[0] as BaseSession;
            var buffer = argsArray[1] as byte[];
            var size = argsArray[2] as int?;
            if (session == null || buffer == null || size == null) return;
            try
            {
                log.DebugFormat("server receive {0} bytes from client.", size);
                session.Receive(buffer, 0, size.Value);
                if (!session.CanDecode()) return;
                var packet = session.Decode();
                NewPacketReceived?.Invoke(session, new NewPacketArgs(packet, session));
            }
            catch (SocketException ex)
            {
                ShutDownClient(session.GetSocket());
            }
        }

        public void Stop()
        {
            status = ServerStatus.Stoping;
            var sockets = new List<Socket>();
            sockets.AddRange(client2Sessions.Keys);
            foreach (var socket in sockets)
            {
                ShutDownClient(socket);
            }
            acceptThread.Join();
            packetThread.Join();
            status = ServerStatus.Closed;
        }

        public ServerStatus Status()
        {
            return status;
        }

        private Socket serverSocket;
        private readonly IList<Socket> clients;
        private readonly ConcurrentDictionary<Socket, BaseSession> client2Sessions;
        private Thread acceptThread, packetThread;
        private ServerStatus status = ServerStatus.None;
        public event NewPacketHandler NewPacketReceived;
        private static readonly int ServerRecieveBufferSize = 10240;
    }

    public enum ServerStatus
    {
        None,
        Running,
        Stoping,
        Closed
    }
}

